// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/input/web_input_event_builders_android.h"

#include <android/input.h>
#include <android/keycodes.h>

#include "base/android/jni_android.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebInputEvent.h"
#include "ui/events/android/key_event_utils.h"
#include "ui/events/gesture_detection/motion_event.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"

using base::android::AttachCurrentThread;
using base::android::ScopedJavaLocalRef;
using blink::WebKeyboardEvent;

namespace {

const int kCombiningAccent = 0x80000000;
const int kCombiningAccentMask = 0x7fffffff;
const int kCompositionKeyCode = 229;

WebKeyboardEvent CreateFakeWebKeyboardEvent(JNIEnv* env,
    int key_code,
    int web_modifier,
    int unicode_character)
{
    ScopedJavaLocalRef<jobject> keydown_event = ui::events::android::CreateKeyEvent(env, ui::MotionEvent::ACTION_DOWN,
        key_code);

    WebKeyboardEvent web_event = content::WebKeyboardEventBuilder::Build(
        env, keydown_event, WebKeyboardEvent::KeyDown, web_modifier, 0, key_code,
        0, unicode_character, false);
    return web_event;
}

} // anonymous namespace

class WebInputEventBuilderAndroidTest : public testing::Test {
};

// This test case is based on VirtualKeyboard layout.
// https://github.com/android/platform_frameworks_base/blob/master/data/keyboards/Virtual.kcm
TEST(WebInputEventBuilderAndroidTest, DomKeyCtrlShift)
{
    JNIEnv* env = AttachCurrentThread();

    struct DomKeyTestCase {
        int key_code;
        int character;
        int shift_character;
    } table[] = {
        { AKEYCODE_0, '0', ')' }, { AKEYCODE_1, '1', '!' }, { AKEYCODE_2, '2', '@' },
        { AKEYCODE_3, '3', '#' }, { AKEYCODE_4, '4', '$' }, { AKEYCODE_5, '5', '%' },
        { AKEYCODE_6, '6', '^' }, { AKEYCODE_7, '7', '&' }, { AKEYCODE_8, '8', '*' },
        { AKEYCODE_9, '9', '(' }, { AKEYCODE_A, 'a', 'A' }, { AKEYCODE_B, 'b', 'B' },
        { AKEYCODE_C, 'c', 'C' }, { AKEYCODE_D, 'd', 'D' }, { AKEYCODE_E, 'e', 'E' },
        { AKEYCODE_F, 'f', 'F' }, { AKEYCODE_G, 'g', 'G' }, { AKEYCODE_H, 'h', 'H' },
        { AKEYCODE_I, 'i', 'I' }, { AKEYCODE_J, 'j', 'J' }, { AKEYCODE_K, 'k', 'K' },
        { AKEYCODE_L, 'l', 'L' }, { AKEYCODE_M, 'm', 'M' }, { AKEYCODE_N, 'n', 'N' },
        { AKEYCODE_O, 'o', 'O' }, { AKEYCODE_P, 'p', 'P' }, { AKEYCODE_Q, 'q', 'Q' },
        { AKEYCODE_R, 'r', 'R' }, { AKEYCODE_S, 's', 'S' }, { AKEYCODE_T, 't', 'T' },
        { AKEYCODE_U, 'u', 'U' }, { AKEYCODE_V, 'v', 'V' }, { AKEYCODE_W, 'w', 'W' },
        { AKEYCODE_X, 'x', 'X' }, { AKEYCODE_Y, 'y', 'Y' }, { AKEYCODE_Z, 'z', 'Z' }
    };

    for (const DomKeyTestCase& entry : table) {
        // Tests DomKey without modifier.
        WebKeyboardEvent web_event = CreateFakeWebKeyboardEvent(env, entry.key_code, 0, entry.character);
        EXPECT_EQ(ui::DomKey::FromCharacter(entry.character), web_event.domKey)
            << ui::KeycodeConverter::DomKeyToKeyString(web_event.domKey);

        // Tests DomKey with Ctrl.
        web_event = CreateFakeWebKeyboardEvent(env, entry.key_code,
            WebKeyboardEvent::ControlKey, 0);
        EXPECT_EQ(ui::DomKey::FromCharacter(entry.character), web_event.domKey)
            << ui::KeycodeConverter::DomKeyToKeyString(web_event.domKey);

        // Tests DomKey with Ctrl and Shift.
        web_event = CreateFakeWebKeyboardEvent(
            env, entry.key_code,
            WebKeyboardEvent::ControlKey | WebKeyboardEvent::ShiftKey, 0);
        EXPECT_EQ(ui::DomKey::FromCharacter(entry.shift_character),
            web_event.domKey)
            << ui::KeycodeConverter::DomKeyToKeyString(web_event.domKey);
    }
}

// This test case is based on VirtualKeyboard layout.
// https://github.com/android/platform_frameworks_base/blob/master/data/keyboards/Virtual.kcm
TEST(WebInputEventBuilderAndroidTest, DomKeyCtrlAlt)
{
    JNIEnv* env = AttachCurrentThread();

    struct DomKeyTestCase {
        int key_code;
        int character;
        int alt_character;
    } table[] = { { AKEYCODE_0, '0', 0 }, { AKEYCODE_1, '1', 0 },
        { AKEYCODE_2, '2', 0 }, { AKEYCODE_3, '3', 0 },
        { AKEYCODE_4, '4', 0 }, { AKEYCODE_5, '5', 0 },
        { AKEYCODE_6, '6', 0 }, { AKEYCODE_7, '7', 0 },
        { AKEYCODE_8, '8', 0 }, { AKEYCODE_9, '9', 0 },
        { AKEYCODE_A, 'a', 0 }, { AKEYCODE_B, 'b', 0 },
        { AKEYCODE_C, 'c', u'\u00e7' }, { AKEYCODE_D, 'd', 0 },
        { AKEYCODE_E, 'e', u'\u0301' }, { AKEYCODE_F, 'f', 0 },
        { AKEYCODE_G, 'g', 0 }, { AKEYCODE_H, 'h', 0 },
        { AKEYCODE_I, 'i', u'\u0302' }, { AKEYCODE_J, 'j', 0 },
        { AKEYCODE_K, 'k', 0 }, { AKEYCODE_L, 'l', 0 },
        { AKEYCODE_M, 'm', 0 }, { AKEYCODE_N, 'n', u'\u0303' },
        { AKEYCODE_O, 'o', 0 }, { AKEYCODE_P, 'p', 0 },
        { AKEYCODE_Q, 'q', 0 }, { AKEYCODE_R, 'r', 0 },
        { AKEYCODE_S, 's', u'\u00df' }, { AKEYCODE_T, 't', 0 },
        { AKEYCODE_U, 'u', u'\u0308' }, { AKEYCODE_V, 'v', 0 },
        { AKEYCODE_W, 'w', 0 }, { AKEYCODE_X, 'x', 0 },
        { AKEYCODE_Y, 'y', 0 }, { AKEYCODE_Z, 'z', 0 } };

    for (const DomKeyTestCase& entry : table) {
        // Tests DomKey with Alt.
        WebKeyboardEvent web_event = CreateFakeWebKeyboardEvent(
            env, entry.key_code, WebKeyboardEvent::AltKey, entry.alt_character);
        ui::DomKey expected_alt_dom_key;
        if (entry.alt_character == 0)
            expected_alt_dom_key = ui::DomKey::FromCharacter(entry.character);
        else if (entry.alt_character & kCombiningAccent)
            expected_alt_dom_key = ui::DomKey::DeadKeyFromCombiningCharacter(
                entry.alt_character & kCombiningAccentMask);
        else
            expected_alt_dom_key = ui::DomKey::FromCharacter(entry.alt_character);
        EXPECT_EQ(expected_alt_dom_key, web_event.domKey)
            << ui::KeycodeConverter::DomKeyToKeyString(web_event.domKey);

        // Tests DomKey with Ctrl and Alt.
        web_event = CreateFakeWebKeyboardEvent(
            env, entry.key_code,
            WebKeyboardEvent::ControlKey | WebKeyboardEvent::AltKey, 0);
        EXPECT_EQ(ui::DomKey::FromCharacter(entry.character), web_event.domKey)
            << ui::KeycodeConverter::DomKeyToKeyString(web_event.domKey);
    }
}

// Testing AKEYCODE_LAST_CHANNEL because it's overlapping with
// COMPOSITION_KEY_CODE (both 229).
TEST(WebInputEventBuilderAndroidTest, LastChannelKey)
{
    JNIEnv* env = AttachCurrentThread();

    // AKEYCODE_LAST_CHANNEL (229) is not defined in minimum NDK.
    WebKeyboardEvent web_event = CreateFakeWebKeyboardEvent(env, 229, 0, 0);
    EXPECT_EQ(229, web_event.nativeKeyCode);
    EXPECT_EQ(ui::KeyboardCode::VKEY_UNKNOWN, web_event.windowsKeyCode);
    EXPECT_EQ(static_cast<int>(ui::DomCode::NONE), web_event.domCode);
    EXPECT_EQ(ui::DomKey::MEDIA_LAST, web_event.domKey);
}

// Synthetic key event should produce DomKey::UNIDENTIFIED.
TEST(WebInputEventBuilderAndroidTest, DomKeySyntheticEvent)
{
    WebKeyboardEvent web_event = content::WebKeyboardEventBuilder::Build(
        nullptr, nullptr, WebKeyboardEvent::KeyDown, 0, 0, kCompositionKeyCode, 0,
        0, false);
    EXPECT_EQ(kCompositionKeyCode, web_event.nativeKeyCode);
    EXPECT_EQ(ui::KeyboardCode::VKEY_UNKNOWN, web_event.windowsKeyCode);
    EXPECT_EQ(static_cast<int>(ui::DomCode::NONE), web_event.domCode);
    EXPECT_EQ(ui::DomKey::UNIDENTIFIED, web_event.domKey);
}

// Testing new Android keycode introduced in API 24.
TEST(WebInputEventBuilderAndroidTest, CutCopyPasteKey)
{
    JNIEnv* env = AttachCurrentThread();

    // The minimum Android NDK does not provide values for these yet:
    enum {
        AKEYCODE_CUT = 277,
        AKEYCODE_COPY = 278,
        AKEYCODE_PASTE = 279,
    };

    struct DomKeyTestCase {
        int key_code;
        ui::DomKey key;
    } test_cases[] = {
        { AKEYCODE_CUT, ui::DomKey::CUT },
        { AKEYCODE_COPY, ui::DomKey::COPY },
        { AKEYCODE_PASTE, ui::DomKey::PASTE },
    };

    for (const auto& entry : test_cases) {
        WebKeyboardEvent web_event = CreateFakeWebKeyboardEvent(env, entry.key_code, 0, 0);
        EXPECT_EQ(entry.key, web_event.domKey);
    }
}
